package ThirdCommunication;


import javax.swing.*;

import java.awt.*;
import java.io.*;
import java.net.Socket;
import java.rmi.server.UID;

import java.util.ArrayList;
import java.util.HashMap;

public class ClientThread extends Thread implements BasicData {
    //服务器IP地址和端口号
    private String serverIP;
    private int port;
    //客户端的各种IO流
    private InputStream ips;
    private OutputStream ops;
    private DataInputStream dips;
    private DataOutputStream dops;
    private boolean isInterrupt = false;//用于断开连接时终止线程
    //客户端-服务器 管道
    private Socket server;
    //窗体类对象
    private UI ui;
    //窗体的在线好友列表
    private JList aliveList;
    //存储所有在线好友id，用于更新aliveList
    private ArrayList<String> aliveIDs = new ArrayList<>();
    //存储当前用户私聊对象的窗体<对方ID，聊天窗体>
    private HashMap<String,PersonalChatFrame> personalFrames = new HashMap<>();

    //存储其它所有对象对当前用户发送的文件个数，用于对缓冲文件命名，防止重名。<对方ID,对方发给你的文件个数>
    private HashMap<String,Integer> fileTimes = new HashMap<String, Integer>();
    String file_buff_path;
    private String save_path;

    public ClientThread(String serverIP,int port,UI ui,JList aliveList) {
        this.serverIP = serverIP;
        this.port = port;
        this.ui = ui;
        this.aliveList = aliveList;
    }
    //搭建管道
    public void connectServer() throws IOException {
        //在构造方法中完成管道的建立和初始化
        server = new Socket(serverIP,port);
        ips = server.getInputStream();
        ops = server.getOutputStream();
        dips = new DataInputStream(ips);
        dops = new DataOutputStream(ops);
    }
    //读取服务器发来的信息

    public void readServer() throws Exception {

        //先读取消息总长
        int totallen = dips.readInt();
        //读取消息类型
        byte type = dips.readByte();
        //读取目标对象
        byte[] target = new byte[20];
        dips.readFully(target);//readFully()表示只有读取字节把目标数组填满，才会停止读取
        String targetID = new String(target).trim();//再转成String，切去补长的'\0'，得到目标对象I
        //读取消息体
        byte[] data = new byte[totallen-4-1-20];
        dips.readFully(data);
        //获取完整的消息包后，对type进行判断，执行不同的封装方法
        if(type==registerType){
            getRegister(data);
        }else if(type==loginType){
            getLogin(targetID,data);
        }else if (type==addUserType) {
            addUser(targetID,data);
        } else if (type==deleteUserType) {
            deleteUser(data);
        } else if (type==messageType) {
            getMessage(targetID,data);
        }else if(type==documentType){
            getFile(targetID,data);
        }
    }
    //向服务器发送信息
    public void writeServer(byte type,String targetID,byte[] data) throws IOException {
        int len = targetLength;//目标对象为定长20的byte[]
        byte[] target = targetID.getBytes();//将targetID转为byte[]
        int totallength = 4+1+20+data.length;//计算总长，不要忘了总长自己占4个字节
        //写入总长
        dops.writeInt(totallength);
        //写入消息类型
        dops.writeByte(type);
        //写入目标对象
        dops.write(target);
        while(len>target.length){//补长
            dops.writeByte('\0');
            len--;
        }
        //写入消息体
        dops.write(data);
    }
    //响应服务器返回的注册结果
    public void getRegister(byte[] data){
        //消息体为注册结果
        String result = new String(data);
        ui.registerAction(result);
    }
    //响应响应服务器返回的登录结果
    public void getLogin(String targetID,byte[] data){
        //消息体为登陆结果
        String result = new String(data);
        ui.loginAction(result);
    }
    //向服务器请求注册
    public void requestRegister(String id,String pwd) throws IOException {
        byte type = registerType;//消息类型
        String targetID = "0";//对象为0表示发给莪服务器
        //消息体,账号+密码为一个40字节的数组，前20为账号，后20为密码，补长
        int len1 = id.getBytes().length;
        int len2 = pwd.getBytes().length;
        for(int i=len1;i<20;i++){
            id += "\0";
        }
        for(int i=len2;i<20;i++){
            pwd += "\0";
        }
        String str = id+pwd;
        byte[] strByte = str.getBytes();
        writeServer(type,targetID,strByte);
    }
    //向服务器请求登录
    public void requestLogin(String id,String pwd) throws IOException {
        byte type = loginType;//消息类型
        String targetID = "0";//对象为0表示发给莪服务器
        //消息体,账号+密码为一个40字节的数组，前20为账号，后20为密码，补长
        int len1 = id.getBytes().length;
        int len2 = pwd.getBytes().length;
        for(int i=len1;i<20;i++){
            id += "\0";
        }
        for(int i=len2;i<20;i++){
            pwd += "\0";
        }
        String str = id+pwd;
        byte[] strByte = str.getBytes();
        writeServer(type,targetID,strByte);
    }
    //响应服务器发来的添加在线用户的请求
    public void addUser(String targetID,byte[] data){
        String newID = new String(data);//新上线的用户id
        //添加在线用户时，把首位设置为当前用户ID
        if(newID.equals(targetID)){
            aliveIDs.add(0,newID);
        }else{
            aliveIDs.add(newID);
        }
        //更新aliveList
        String[] userIDs = new String[aliveIDs.size()];
        for(int i=0;i<aliveIDs.size();i++){
            userIDs[i] = aliveIDs.get(i);
        }
        aliveList.setListData(userIDs);
    }
    //响应服务器发来的删除在线用户的请求
    public void deleteUser(byte[] data){
        String newID = new String(data);//下线的用户id
        aliveIDs.remove(newID);
        //更新aliveList
        String[] userIDs = new String[aliveIDs.size()];
        for(int i=0;i<aliveIDs.size();i++){
            userIDs[i] = aliveIDs.get(i);
        }
        aliveList.setListData(userIDs);

    }
    //向服务器请求退出
    public void requestQuit(String id) throws IOException {
        byte type = quitType;
        String targetID = "0";
        //退出只要发送id就行，消息体定位20长
        int len = id.getBytes().length;
        for(int i=len;i<20;i++){
            id += "\0";
        }
        byte[] idByte = id.getBytes();
        writeServer(type,targetID,idByte);
        isInterrupt = true;//最后终止线程
    }
    //接收服务器发来的聊天消息
    public void getMessage(String targetID,byte[] data){
        //聊天信息的消息体组成为1群发or私发+20消息来源+length消息内容
        byte type = data[0];
        byte[] fromByte = new byte[20];
        byte[] messageByte = new byte[data.length-1-20];
        for(int i=0;i<20;i++){
            fromByte[i] = data[1+i];
        }
        for(int i=0;i<data.length-1-20;i++){
            messageByte[i] = data[1+20+i];
        }
        String fromID = new String(fromByte).trim();//消息来源ID
        String message = new String(messageByte);//消息内容
        //对自己发的消息进行标记
        if(targetID.equals(fromID)){
            message = fromID+"(yourself): "+message;
        }else{
            message = fromID+": "+message;
        }
        if(type==0){//群发，显示在公屏上
            ui.showMessage(message);
        }else{//私发
            //先判断私聊对象的窗体是否已经创建过
            if(personalFrames.containsKey(fromID)){//已经创建,则直接显示
               personalFrames.get(fromID).showMessage(message);
            }else{//未创建窗体，则创建
                PersonalChatFrame pcf = new PersonalChatFrame(targetID,fromID,this);
                personalFrames.put(fromID,pcf);//加入私聊窗体队列
                pcf.showUI();
                pcf.showMessage(message);
            }
        }
    }
    //向服务器发送信息
    public void sendMessage(String targetID,String message) throws IOException {
        byte type = messageType;
        writeServer(type,targetID,message.getBytes());
    }
    //鼠标点击列表用户targetID，开启私聊窗体
    public void requestPersonalFrame(String myID,String targetID){
        if(!personalFrames.containsKey(targetID)){//如果私聊窗体未创建
            PersonalChatFrame pcf = new PersonalChatFrame(myID,targetID,this);
            personalFrames.put(targetID,pcf);
            pcf.showUI();
        }
    }
    //关闭对用户toID的私聊窗体
    public void requestClosePersonalFrame(String toID){
        personalFrames.remove(toID);
    }
    //向服务器发送文件

    public void sendFile(String myID,String targetID,String path) throws Exception {
        /*
        //读取文本
        File file = new File(path);
        InputStream is = new FileInputStream(file);
        System.out.println("测试文件可读取的字节数为："+is.available());
        //is.available()返回一个int值，当文件可读取字节数超过int表示范围时只能返回int所能表示的最大值
        //比如一个10G文件可读取的字节数就超过了int表示范围

        byte[] filebyte = new byte[is.available()];//available获取该流可读取的字节数
        is.read(filebyte);
        is.close();
        writeServer(documentType,targetID,filebyte);

        */
        int process=1;//切分发送的文件段序号
        File file = new File(path);
        InputStream ips = new FileInputStream(file);
        //ByteArrayOutputStream()用于实现读取数据是在流中原有数据的基础上进行，而不是覆盖。确保第0位位process
        ByteArrayOutputStream baos;
        //获取文件名，并根据消息规范，把文件名拼到60个字节的长度
        String fileName = file.getName();
        for(int i=fileName.getBytes().length;i<60;i++){
            fileName += "\0";
        }
        //存储文件名的字节数组
        byte[] fileNamebyte = fileName.getBytes();
        //存储文件段的字节数组
        byte[] buff = new byte[8192];
        //每次从文件中读取的文件段长度
        int len;
        //发送过程中开启过渡窗体
        ui.showFileLoadingUI(1);
        //客户端发送的文件data[]，4+60+len
        while((len=ips.read(buff))!=-1){
            baos = new ByteArrayOutputStream();
            baos.write(InttoBytes(process));
            baos.write(fileNamebyte);
            //从buff数组的第0位开始写入长度为len的字节
            baos.write(buff,0,len);
            //文件段序号递增
            process++;
            //分段发送
            writeServer(documentType,targetID,baos.toByteArray());
        }
        //当文件段全部发送完毕时发送结束信号，即data[]只有process+fileName||4+60
        baos = new ByteArrayOutputStream();
        baos.write(InttoBytes(-1));
        baos.write(fileNamebyte);
        writeServer(documentType,targetID,baos.toByteArray());
        //发送完毕，关闭过渡窗体
        ui.closeFileLoadingUI();
    }
    //接收服务器发来的文件
    public void getFile(String targetID,byte[] data) throws Exception {
        //文件信息的消息体组成为1群发or私发+20消息来源+4文件段序号+60文件原名+length文件内容
        byte type = data[0];
        byte[] fromByte = new byte[20];
        byte[] processbyte = new byte[4];
        byte[] fileNamebyte = new byte[60];
        byte[] messageByte = new byte[data.length-1-20-4-60];
        //读取发送方ID
        for(int i=0;i<20;i++){
            fromByte[i] = data[1+i];
        }
        //读取文件段序号
        for(int i=0;i<4;i++){
            processbyte[i] = data[1+20+i];
        }
        //读取文件原名
        for(int i=0;i<60;i++){
            fileNamebyte[i] = data[1+20+4+i];
        }
        for(int i=0;i<data.length-1-20-4-60;i++){
            messageByte[i] = data[1+20+4+60+i];
        }
        String fromID = new String(fromByte).trim();//消息来源ID
        String fileName = new String(fileNamebyte).trim();//文件原名
        int process = BytoInt(processbyte);//文件段序号
        //先把传过来的文件段全部写入本地默认文件夹
        if(process==1){//第一个文件包，在默认文件夹中创建文件并写入
            //查找文件次数哈希表找到对方发送的文件次数，并以此命名
            if(fileTimes.containsKey(fromID)){
                fileTimes.put(fromID,fileTimes.get(fromID)+1);
            }else{
                fileTimes.put(fromID,1);
            }
            //文件命名：发送方ID+"-"+接收方ID+"-"发送方发给当前用户的所有文件数
            file_buff_path = "D:\\java\\idea\\AllProjects\\ChatRoom\\ClientProject\\FileBuffer\\"+fromID+"-"+targetID+"-"+fileTimes.get(fromID)+".txt";
            File file = new File(file_buff_path);
            FileOutputStream fos = new FileOutputStream(file,true);
            fos.write(messageByte);
            fos.flush();
            fos.close();
        }else if(process>1){//中间的文件包，根据哈希表找对应文件并写入
            file_buff_path = "D:\\java\\idea\\AllProjects\\ChatRoom\\ClientProject\\FileBuffer\\"+fromID+"-"+targetID+"-"+fileTimes.get(fromID)+".txt";
            File file = new File(file_buff_path);
            FileOutputStream fos = new FileOutputStream(file,true);
            fos.write(messageByte);
            fos.flush();
            fos.close();
        }else if(process==-1){//当文件传输结束，即process=-1时，再提示要不要接收文件
            if(type!=0&& !personalFrames.containsKey(fromID)){//私聊窗体未创建
                PersonalChatFrame pcf = new PersonalChatFrame(targetID,fromID,this);
                personalFrames.put(fromID,pcf);//加入私聊窗体队列
                pcf.showUI();
            }
            ui.showFileReceiveUI(fromID);
        }
    }
    //接收文件后写入文件,path为接收的文件要存入的路径
    public void writeFile(String path,String fromID,String myID) throws IOException {
        //展示文件过渡窗体
        ui.showFileLoadingUI(2);
        File file = new File(path);
        //FileOutputStream构造方法第二个参数表示是否追加数据，true为在原文件数据基础上追加写入的数据，false为先清空文件再写入数据
        FileOutputStream fileOutputStream = new FileOutputStream(file,true);
        //根据fromID和fileTimes拼接出缓冲文件夹中的文件路径
        String file_buff_path = "D:\\java\\idea\\AllProjects\\ChatRoom\\ClientProject\\FileBuffer\\"+fromID+"-"+myID+"-"+fileTimes.get(fromID)+".txt";
        File fromfile = new File(file_buff_path);
        InputStream fromips = new FileInputStream(fromfile);
        byte[] buff = new byte[8192];
        int len;
        while((len=fromips.read(buff))!=-1){
            fileOutputStream.write(buff,0,len);
        }
        fileOutputStream.flush();
        fileOutputStream.close();
        //关闭文件过渡窗体
        ui.closeFileLoadingUI();
    }
    public  byte[] InttoBytes(int i){
        byte[] arr = new byte[4] ;
        arr[0] = (byte)i ;         //通过debug可以看到arr[0] = -23,也就是10010111
        arr[1] = (byte)(i >> 8) ;  //通过debug可以看到arr[1] = -18,也就是10010010
        arr[2] = (byte)(i >> 16) ; //通过debug可以看到arr[2] = 51, 也就是00110011
        arr[3] = (byte)(i >> 24) ; //通过debug可以看到arr[3] = 1,  也就是00000001
        return arr;
    }
    public  int BytoInt(byte[] bytes){
        int i0= bytes[0] & 0xFF  ;
        int i1 = (bytes[1] & 0xFF) << 8 ;
        int i2 = (bytes[2] & 0xFF) << 16 ;
        int i3 = (bytes[3] & 0xFF) << 24 ;
        return i0 | i1 | i2 | i3 ;

    }
    //发送通过特定事件触发，客户端要持续做的是接收
    public void run(){
        while(!isInterrupt){
            try {
                readServer();

            } catch (Exception e) {

                throw new RuntimeException(e);
            }
        }
    }
}
